normal modeをJavascript Objectで表現してみる
利点
vim commandsをparseしたobjectとして扱える
command実行に必要な情報を全て詰め込める
文字列を解析すると同時に、実行可能なcommandを構築していく感じ
出来たObject model
code:ts
interface VimCommand {
command: (params: object) => void:
params?: {
register?: Register;
count?: number;
char?: string;
range?: {
command: (props: object) => Selections;
params?: {
count?: number;
}
}
};
}
Selectionsは選択範囲を表すobject
paramsにcommandに渡すparameterを全部詰め込む
どんなコマンドでもcommand(params)で実行できるようになる
Registerはstringの部分型
使用可能なレジスタの名前を羅列しただけ
rangeはoperatorの処理範囲を決めるやつ
commandにはVimCommand.commandの一部を流用できる
motionとか
{visual: true}を渡すと、scrapbox上に選択範囲を出して、その情報を返却する
選択範囲を常に返すようにしておく
選択はしない
Visual modeで使えるようにするために選択範囲を出すつもりだったのだ
しかしよく思い出すと、Visual modeの選択範囲の挙動は普通のテキストエディタとは違う。
選択範囲の情報だけ渡してもらって、Visual modeで挙動を調節するとかしたほうが良さそうだ
Visual ModeはVisual Modeで別途commandを用意した方が良い気がしてきた
選択範囲の縮小/拡張が結構独特
選択範囲の状況に応じて考えないといけないことが結構ある
text objectも定義しておく
aw/iwなど
cursor移動はせず、単に選択範囲を返す関数として定義する
コマンドの解析状態を表す変数も用意したほうがいい
code:ts
interface ParseState {
state: 'operator' | 'count' | 'register' | ... :
}
解析する時、投げられた文字がどのcommandに当たるのかを調べる必要があるのか
例えば何も入力していない状態で"が入ってきたら、register modeに移行する
以後、{'a','b',..,'unnamed','*'}に前方一致するcommandかどうかを判定する
matchしなかったら不正なcommandとしてその場で破棄 (<Esc>相当)
matchした候補が複数あったらそのまま待機
候補が一つに絞られたらVimCommand.params.registerに格納し、状態をoperatorに移動する
一つ一つのstateに必要なのはこれらかな
コマンドが不正かどうか判定する条件式
不正の場合は<ESC>を発動しEntryStateに戻る
コマンドが確定したかどうか判定する条件式
コマンド確定後に移動するstate
未処理のコマンド文字列
次のstateに渡す
これはState管理クラスが持っていても良いかも
presentState.parse((newState, restSequence)=>this.changeState(newState,restSequence),newChar);
必要なParseStateの種類
EntryState
Normal Modeで何も入力されていない状態のときのstate
数字が入力されたらCountStateに移行する
入力された数字を渡す必要があるのかtakker.icon
未処理の文字として渡せばいいか。
どのstateに移行するか確定できない文字の場合は待機
ユーザー定義コマンドもここで解析する
設定に応じてどのParseStateのcommand listに登録されるかは変化する
OperatorState
operatorの入力を受け付ける
operatorをuserが作れるようにすると面白いかな
確定後MotionOrCountStateに移行する
MotionOrCountState
OperatorStateからCountState/MotionStateへ移行するつなぎ
数字が入力されたらCountStateに、Motionに前方一致する文字列が入力されたらMotionStateに移行する
CountState
繰り返し回数の入力を受け付ける
OperatorStateから移行した場合は、VimCommand.range.params.countに格納する
移行元の情報を持たせるべきか。
constructorから文字列で注入するとか
このstateは不正な値かどうかの検証をしない
数字以外が投げたれたら、EntryStateに移行する
RegisterState
Register入力を受け付ける
確定後OperatorStateに移行する
MotionState
Motion入力を受け付ける
確定したらcommandを実行する
OperatorState経由の場合は、VimCommand.range.commandに格納する
どのstateに移るのか決めるためのstateが必要なんだな
MotionOrCountState
CharState
文字入力を一つだけ受け付ける
確定したらcommandを実行する
試行錯誤
1文字cursor移動/単語移動/スクロール/インデント/undo/redo
{command: moveLeft, params: {count: 1}}
それぞれ個別に関数を作っておく
実行するときはmoveLeft(params)とする
行内移動
{command: jumpLF}
実行方法:jumpLF({})
countは無視する
f/t
{command: motion_f, params: {count: 2, char: 'a'}}
jump先の文字を指定する必要がある
行移動
gg/G/H/M/Lはcount無視
それ以外はcount付きで
Insert Mode突入
iaIAoO
どれも{command: enterInsertModeBefore}などとしておく
mode変更はどうやって通知しようか?
更新関数を渡す
置換
~は……countいらないか
r: {command: replace, param: {char: 'あ'}}
切り取り/yank/paste
{command: delete, params: {register: 'unnamed', count: 2, range: {command: moveRight, params: {count: 1}}}}
xはdlとみなして一つのコマンドで賄う
{command: yank, params: {register: 'unnamed', count: 2, range: {command: moveRight, params: {count: 1}}}}
macro
{command: record, params: {register: 'a', char: 'ggVGd'}}
charに複数文字を入れられるようにしておく